package main

import "fmt"

/*
函数参数传递介绍
*/
func main() {

	/*
		一. 值传递（传值）。值传递是指【在调用函数时】将实际参数复制一份传递到函数中，这样在函数中如果对参数进行修改，将不会影响到原内容数据

			TIPS：
				· Go语言使用的是值传递，即在调用过程中不会影响到原内容数据；
				· 每次调用函数，都将实参复制一份再传递到函数中。每次都复制一份，性能会下降。但是在Go语言中使用指针和值传递配合就能避免性能降低问题，也就是通过传指针参数来解决实参复制的问题
	*/
	var aa int = 4
	fmt.Println(aa)
	fmt.Printf("aa的内存地址为 %d \n", &aa)
	changeA(aa)
	// 通过打印可以看到变量 a 的值并没有改变
	fmt.Println(aa)
	fmt.Printf("aa的内存地址为 %d \n", &aa)

	/*
		二. 引用传递（传引用）。引用传递是指【在调用函数时】将实际参数的地址作为指针类型传递到函数中，那么在函数中对参数所进行的修改，将影响到原内容数据
			TIPS：
				· 严格来说Go语言只有值传递这一种传参方式，Go语言是没有引用传递的。Go语言中可以【借助传指针来实现引用传递的效果】。函数参数使用指
				  针参数，传参的时候其实是复制一份指针参数，也就是复制了一份变量地址。这样就不用担心实参复制造成的内存浪费、时间开销、性能降低
				· Go语言中所有的传参都是值传递（传值），都是一个副本。副本的内容有的是值类型（int、string、bool、array、struct属于值类型），
				  这样在【函数中】就无法修改原内容数据；有的是引用类型（pointer、 slice、map、chan属于引用类型），也就可以修改原内容数据
				· 注意引用类型和引用传递说法的区别

			引用传递的作用：
				• 传指针使得多个函数能操作同一个对象；
				• 传指针更轻量级（8 bytes），只需要传内存地址。如果参数是非指针参数，那么值传递的过程中，每次在复制上面就会花费相对较多的系统开销（内存和时间）。
				  所以要传递大的结构体的时候，用指针是一个明智的选择。Go语言中slice、map、chan类型的实现机制都类似指针，所以可以直接传递，而不必取地址后传递指针
	*/
	changeAPtr(&aa)
	// 可以看到变量a的值改变了
	fmt.Println(aa)

	// slice型参数验证
	a := []int{1, 2, 3, 4, 5, 6}
	fmt.Printf("1. 变量a的内存地址为：%p , 切片型变量a的内存地址为：%p , 值为：%v \n", &a, a, a)
	// 值传递（但是是应用类型）
	changeSlice(a)
	fmt.Printf("2. 变量a的内存地址为：%p , 切片型变量a的内存地址为：%p , 值为：%v \n", &a, a, a)
	// 引用传递
	changeSlice2(&a)
	fmt.Printf("3. 变量a的内存地址为：%p , 切片型变量a的内存地址为：%p , 值为：%v \n", &a, a, a)
}

func changeA(i int) {
	// 通过打印可以看到变量形参i的内存地址和变量值aa的内存地址不同了
	fmt.Println(i)
	fmt.Printf("i的内存地址为 %d \n", &i)
	// 对形参重新赋值
	i = 30
	fmt.Println(i)
}
func changeAPtr(ptr *int) {

	// 通过打印可以看到变量形参ptr的内存地址和变量值aa的内存地址相同
	fmt.Printf("ptr的内存地址为 %d \n", ptr)
	*ptr = 30
}

func changeSlice(a []int) {

	fmt.Printf("-------changeSlice函数内：值参数a的内存地址为：%p, 切片型变量a的内存地址为：%p，值为：%v \n", &a, a, a)
	a[0] = 99
}

func changeSlice2(a *[]int) {

	fmt.Printf("-------changeSlice2函数内：值参数a的内存地址为：%p, 切片型变量a的内存地址为：%p，值为：%v \n", &a, *a, a)
	(*a)[0] = 250
}
